﻿Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Description
Imports System.Runtime.Serialization
Imports System.IO
Imports System.ServiceModel.Channels
Imports System.Text

Imports log4net
Imports System.Web.Script.Serialization
Imports VPager.Display
Imports VPager.Enumerations
Imports System.Net
Imports VPager.Exceptions

Namespace WebService


    <ServiceContract()> _
    Public Interface IPageService

        <OperationContract()> _
        <WebInvoke(method:="OPTIONS", UriTemplate:="*")> _
        Sub Handle_CORS()

        <OperationContract()> _
        <WebInvoke(method:="POST", UriTemplate:="/pages/type/parent", _
                   RequestFormat:=WebMessageFormat.Json)> _
        Function AddParentPage(ByVal page As ParentPage) As Stream

        <OperationContract()> _
        <WebInvoke(method:="POST", UriTemplate:="/pages/type/driver", _
                   RequestFormat:=WebMessageFormat.Json)> _
        Function AddDriverPage(ByVal page As DriverPage) As Stream

        <OperationContract()> _
        <WebInvoke(method:="PUT", UriTemplate:="/pages/type/parent/{page_id}", _
                   RequestFormat:=WebMessageFormat.Json)> _
        Function ModifyParentPage(ByVal page_id As String, ByVal page As ParentPage) As Stream

        <OperationContract()> _
        <WebInvoke(method:="PUT", UriTemplate:="/pages/type/driver/{page_id}", _
                   RequestFormat:=WebMessageFormat.Json)> _
        Function ModifyDriverPage(ByVal page_id As String, ByVal page As DriverPage) As Stream

        <OperationContract()> _
        <WebInvoke(method:="DELETE", UriTemplate:="/pages/id/{page_id}", _
                   ResponseFormat:=WebMessageFormat.Json)> _
        Function ClearPage(ByVal page_id As String) As Boolean

        <OperationContract()> _
        <WebInvoke(method:="DELETE", UriTemplate:="/pages", _
                   ResponseFormat:=WebMessageFormat.Json)> _
        Function ClearAllPages() As Integer

        <OperationContract()> _
        <WebGet(UriTemplate:="/pages/id/{page_id}")> _
        Function GetPage(ByVal page_id As String) As Stream

        <OperationContract()> _
        <WebGet(UriTemplate:="/pages")> _
        Function GetPageList() As Stream

        <OperationContract()> _
        <WebGet(UriTemplate:="/display/current")> _
        Function GetActivePage() As Stream

        <OperationContract()> _
        <WebInvoke(method:="PUT", UriTemplate:="/display/params", _
                   RequestFormat:=WebMessageFormat.Json)> _
        Sub SetDisplayHandlerParams(ByVal displayState As DisplayParams)


        <OperationContract()> _
        <WebGet(UriTemplate:="/info", ResponseFormat:=WebMessageFormat.Json)> _
        Function GetServiceInfo() As ServiceInfo

        <OperationContract()> _
        <WebInvoke(method:="PUT", UriTemplate:="/status/{state}", _
                   RequestFormat:=WebMessageFormat.Json, _
                   ResponseFormat:=WebMessageFormat.Json)> _
        Function _ModifyStatus(ByVal state As String) As Boolean

        <OperationContract()> _
        <WebGet(UriTemplate:="/file/{filename}")> _
        Function GetFile(ByVal filename As String) As Stream

        <OperationContract()> _
        <WebGet(UriTemplate:="/client")> _
        Function GetClient() As Stream

    End Interface

    <ServiceBehavior()> _
    Public Class PagerWebService
        Implements IPageService

        ' (Pseudo) CONST types below for simple Web File Serving to support vPager Web Client
        Const WEB_CLIENT As String = "TestClient.html"
        Const WEB_FILE_DIR = "files\"

        Const HTTP_HEAD_ACAO = "Access-Control-Allow-Origin"

        Private Shared MEDIA_HTML_EXTS As String() = {".html", ".js"}
        Private Shared MEDIA_HTML_TYPES As String() = {"text/html", "text/javascript"}
        Private Shared MEDIA_TYPES As Hashtable = Nothing

        ' Shared Class variables for Service use
        Private Shared sController As Controller = Controller.getInstance()
        Private Shared sLog As ILog = LogManager.GetLogger(GetType(PagerWebService))
        Private Shared sSelfHost As WebServiceHost

#Region "Web Interface Methods"
        Public Sub New()
            ' If first time class is instantiated then setup file extension / mime type
            ' mapping for simple web file serving to support built in Web Client.
            If (IsNothing(MEDIA_TYPES)) Then
                MEDIA_TYPES = New Hashtable()
                For idx As Integer = 0 To MEDIA_HTML_EXTS.Length - 1
                    MEDIA_TYPES.Add(MEDIA_HTML_EXTS(idx), MEDIA_HTML_TYPES(idx))
                Next
            End If
        End Sub

        Public Function AddParentPage(ByVal page As ParentPage) As Stream Implements IPageService.AddParentPage
            Return HttpResponseAsJson(_AddPage(page))
        End Function

        Public Function AddDriverPage(ByVal page As DriverPage) As Stream Implements IPageService.AddDriverPage
            Return HttpResponseAsJson(_AddPage(page))
        End Function

        Public Function ModifyParentPage(ByVal page_id As String, ByVal page As ParentPage) As Stream Implements IPageService.ModifyParentPage
            Return HttpResponseAsJson(_ModifyPage(page_id, page))
        End Function

        Public Function ModifyDriverPage(ByVal page_id As String, ByVal page As DriverPage) As Stream Implements IPageService.ModifyDriverPage
            Return HttpResponseAsJson(_ModifyPage(page_id, page))
        End Function

        Public Function ClearPage(ByVal page_id As String) As Boolean Implements IPageService.ClearPage
            Dim rv = False

            sLog.Debug("The ClearPage(" + page_id + ") called.")

            If (page_id IsNot Nothing) Then
                rv = sController.PageList.pageDelete(page_id)
            End If

            If rv Then
                setResponseStatus()
            Else
                setResponseStatus(HttpStatusCode.NotFound, "Page Item Not Found.")
            End If

            Return rv

        End Function

        Public Function ClearAllPages() As Integer Implements IPageService.ClearAllPages
            sLog.Debug("The ClearAllPages() called.")

            Dim count As Integer = sController.PageList.getCount()
            sController.PageList.clear()
            setResponseStatus()

            Return count

        End Function

        Public Function GetActivePage() As Stream Implements IPageService.GetActivePage
            Dim rv As PageItem = Nothing
            sLog.Debug("GetActivePage() called.")

            ' Set Default success case, failures will override response below
            setResponseStatus()

            If sController.IsDisplayActive Then
                rv = sController.Display.CurrentPage
                If rv Is Nothing Then
                    setResponseStatus(HttpStatusCode.NotFound, "Requested Page Item Not Found.")
                End If
            Else
                setResponseStatus(HttpStatusCode.NotFound, "Display is Not Active.")
            End If

            Return HttpResponseAsJson(rv)

        End Function

        Sub SetDisplayHandlerParams(ByVal params As DisplayParams) Implements IPageService.SetDisplayHandlerParams

            ' Set Default success case, failures will override response below
            setResponseStatus()

            If params IsNot Nothing And params.IsValid Then
                sLog.Debug("SetDisplayHandlerParams( ViewState = " + params.ViewState.ToString + " ) called.")

                Dim success = True
                Select Case params.ViewState
                    Case AbledState.ENABLED
                        sController.startPagerDisplay()
                    Case AbledState.DISABLED
                        success = sController.closePagerDisplay(params.ForceDisable)
                End Select
                If Not success Then
                    setResponseStatus(HttpStatusCode.Conflict, "Cannot DISABLE Display with Active Pages.")
                End If
            Else
                sLog.Debug("SetDisplayHandlerParams( <NULL> ) called.")
                setResponseStatus(HttpStatusCode.BadRequest, "Invalid Display Parameter(s).")
            End If

        End Sub

        Function GetPage(ByVal pageID As String) As Stream Implements IPageService.GetPage
            Dim rv As PageItem = Nothing

            sLog.Debug("GetPage(" + pageID + ") called.")

            ' Set Default success case, failures will override response below
            setResponseStatus()

            rv = sController.PageList.getPageByID(pageID)
            If rv Is Nothing Then
                setResponseStatus(HttpStatusCode.NotFound, "Requested Page Item Not Found.")
            End If

            Return HttpResponseAsJson(rv)

        End Function

        Function GetClient() As Stream Implements IPageService.GetClient

            sLog.Debug("GetClient() called.")

            Return GetFile(WEB_CLIENT)

        End Function

        Function GetFile(ByVal filename As String) As Stream Implements IPageService.GetFile

            sLog.Debug("GetFile(" + filename + ") called.")

            Dim stream As FileStream = Nothing
            Dim fileInfo As New FileInfo(WEB_FILE_DIR + filename)
            Dim response = WebOperationContext.Current.OutgoingResponse

            If (fileInfo.Exists) Then
                sLog.Debug("Found file: " + fileInfo.FullName)
                If (MEDIA_TYPES.ContainsKey(fileInfo.Extension.ToLower)) Then
                    sLog.Info("File Request: Granted for " + fileInfo.Name)
                    response.ContentType = MEDIA_TYPES.Item(fileInfo.Extension.ToLower)
                    response.Headers.Add("Content-Length", fileInfo.Length.ToString())
                    response.Headers.Add("Access-Control-Allow-Origin", "*")
                    stream = New FileStream(fileInfo.FullName, FileMode.Open)
                Else
                    sLog.Warn("File Request: DENIED for " + fileInfo.Name)
                End If
            Else
                sLog.Warn("File Request: NOT FOUND for " + fileInfo.Name)
            End If

            Return stream

        End Function

        Public Function GetPageList() As Stream Implements IPageService.GetPageList

            sLog.Debug("GetPageList() called.")

            setResponseStatus()
            Return HttpResponseAsJson(sController.PageList.getList())

        End Function

        Public Function GetServiceInfo() As ServiceInfo Implements IPageService.GetServiceInfo
            sLog.Debug("GetServiceInfo() called.")
            setResponseStatus()

            Return sController.ServiceInfo

        End Function

        Sub Handle_CORS() Implements IPageService.Handle_CORS

            sLog.Debug("Handle_CORS() called.")

            'for all cors requests  
            WebOperationContext.Current.OutgoingResponse.Headers.Add(HTTP_HEAD_ACAO, "*")
            WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
            WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers",
                "Content-Type, Accept, Authorization, x-requested-with")

        End Sub

        Public Function _AddPage(ByVal page As PageItem) As PageItem
            Dim rv As PageItem = Nothing
            Dim req_type = "<not specified>"
            Dim req_data = "<null>"

            If (page IsNot Nothing) Then
                req_data = page.ToString
                req_type = page.GetType.ToString
            End If
            sLog.Debug("_AddPage(" + req_data +
                       ") called. Page Type:" +
                       req_type)

            If (page IsNot Nothing) Then
                Try
                    ' Stamp page with client's IP Address
                    Dim props As MessageProperties = OperationContext.Current.IncomingMessageProperties
                    Dim addr As RemoteEndpointMessageProperty = props.Item("System.ServiceModel.Channels.RemoteEndpointMessageProperty")
                    page.Source = addr.Address.ToString

                    rv = sController.PageList.requestPageAdd(page)
                Catch ex As ArgumentException
                    rv = Nothing
                    setResponseStatus(HttpStatusCode.Conflict, "Page Data Invalid.")
                Catch ex As PageExistsException
                    rv = Nothing
                    setResponseStatus(HttpStatusCode.Forbidden, "Page with Similar Data Already Exists.")
                End Try
            Else
                setResponseStatus(HttpStatusCode.BadRequest, "Page Data Incomplete.")
            End If

            If (rv IsNot Nothing) Then
                setResponseStatus(HttpStatusCode.Created, "Success")
            End If

            Return rv

        End Function

        Public Function _ModifyPage(ByVal page_id As String, ByVal page As PageItem) As PageItem
            Dim rv As PageItem = Nothing
            Dim curr_page As PageItem = Nothing
            Dim req_data = "<null>"

            If (page IsNot Nothing) Then
                req_data = page.ToString
            End If
            sLog.Debug("_ModifyPage(" + page_id + ", " + req_data + ") called.")

            WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*")
            If (page IsNot Nothing AndAlso
                page_id IsNot Nothing AndAlso
                page_id <> "") _
            Then

                If (page.Validate()) Then
                    Try
                        curr_page = sController.PageList.getPageByID(Long.Parse(page_id))
                        If (curr_page IsNot Nothing) Then
                            curr_page.ModifyWith(page)
                            rv = curr_page
                        Else
                            setResponseStatus(HttpStatusCode.NotFound, "Page Not Found for Specified ID.")
                        End If
                    Catch ex As Exception
                        setResponseStatus(HttpStatusCode.Conflict, "Page ID Invalid.")
                    End Try
                Else
                    setResponseStatus(HttpStatusCode.Conflict, "Page Data Invalid.")
                End If
            Else
                setResponseStatus(HttpStatusCode.BadRequest, "Page Data Incomplete.")
            End If

            Return rv

        End Function

        Function _ModifyStatus(ByVal state As String) As Boolean Implements IPageService._ModifyStatus
            'serviceInfo.Status.OverallStatus = state
            Return True
        End Function

        Private Function HttpResponseAsJson(ByRef obj As Object) As Stream
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8"
            Return GetJsonAsStream(obj)
        End Function

        Private Function GetJsonAsStream(ByRef obj As Object) As Stream
            Dim stream As MemoryStream = New MemoryStream(Encoding.UTF8.GetBytes(GetJson(obj)))
            Return stream
        End Function

        Function GetJson(ByRef obj As Object) As String
            Dim json As String = (New JavaScriptSerializer()).Serialize(obj)
            Debug.WriteLine("JSON RAW: " + json)
            Return json
        End Function

        Sub setResponseStatus()
            setResponseStatus(HttpStatusCode.OK, "Success")
        End Sub

        Sub setResponseStatus(ByVal statusCode As HttpStatusCode, ByVal statusDesc As String)
            Dim response As OutgoingWebResponseContext = WebOperationContext.Current.OutgoingResponse

            response.StatusCode = statusCode
            response.StatusDescription = statusDesc

            If Not response.Headers.AllKeys.Contains(HTTP_HEAD_ACAO) Then
                response.Headers.Add(HTTP_HEAD_ACAO, "*")
            End If

        End Sub

#End Region

#Region "Service Command and Control Methods"

        Public Shared Function startService(ByVal baseURI As String) As Boolean

            Dim success As Boolean = False
            Dim baseAddress As Uri

            setupLogging()

            sLog.Debug("Starting Web Service...")

            Try
                baseAddress = New Uri(baseURI)
                sSelfHost = New WebServiceHost(GetType(PagerWebService), baseAddress)
                sSelfHost.AddServiceEndpoint(GetType(IPageService), New WebHttpBinding(), "")
                Dim sdb As ServiceDebugBehavior = sSelfHost.Description.Behaviors.Find(Of ServiceDebugBehavior)()
                sdb.HttpHelpPageEnabled = False
                ' Launch Web Service
                sSelfHost.Open()
                success = True
                sLog.Info("Web Service bound to Base URI: " + baseURI)
                sLog.Info("Web Service successfully started.")
            Catch ce As CommunicationException
                sLog.Error("An exception occurred starting Web Service: " + ce.Message)
                If (Not IsNothing(sSelfHost)) Then
                    sSelfHost.Abort()
                End If
            Catch ex As Exception
                sLog.Fatal("An system exception occurred starting Web Service: " + ex.Message, ex)
                If (Not IsNothing(sSelfHost)) Then
                    sSelfHost.Abort()
                End If
            End Try

            Return success

        End Function

        Public Shared Function stopService() As Boolean
            Dim success As Boolean = False

            sLog.Debug("Stopping Web Service...")

            Try
                If (Not IsNothing(sSelfHost)) Then
                    sSelfHost.Close()
                End If
                success = True
                sLog.Info("Web Service successfully shutdown.")
            Catch ce As CommunicationException
                sLog.Error("An exception occurred stopping Web Service: " + ce.Message)
                If (Not IsNothing(sSelfHost)) Then
                    sSelfHost.Abort()
                End If
            End Try

            Return success

        End Function

        Private Shared Sub setupLogging()
            If (IsNothing(sLog)) Then
                sLog = LogManager.GetLogger(GetType(PagerWebService))
            End If
        End Sub

#End Region

    End Class


End Namespace
